#! /usr/bin/python
# -*- python -*-
# -*- coding: utf-8 -*-
#   tuna - Application Tuning GUI
#   Copyright (C) 2009 Arnaldo Carvalho de Melo
#   Arnaldo Carvalho de Melo <acme@redhat.com>
#
#   This application is free software; you can redistribute it and/or
#   modify it under the terms of the GNU General Public License
#   as published by the Free Software Foundation; version 2.
#
#   This application is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#   General Public License for more details.

import filecmp, getopt, os, posix, signal, sys, tempfile

regtest_output_dir = "/media/tb/pahole/regtest/"
regtest_obj_dir = "/media/tb/debuginfo/usr/lib/debug/"
tools = {"pahole": { "dwarf": "--flat_arrays --show_private_classes --fixup_silly_bitfields --first_obj_only --classes_as_structs" }}
all_formats = ("ctf", "dwarf")
formats = all_formats
len_debug_dir = len(regtest_obj_dir)
verbose = 1

# Turn this on when testing CTF generated files
use_options = False

def diff_file(from_filename, to_filename):
	fd, diff_filename = tempfile.mkstemp()
	command = 'diff -up "%s" "%s" > %s' % (from_filename,
					       to_filename, diff_filename)
	if verbose > 1:
		print command
	try:
		os.system(command)
		os.system("vim %s" % diff_filename)
	finally:
		os.unlink(diff_filename)

def dir_has_no_diffs(dirname):
	return os.access(os.path.join(dirname, ".no_diffs"), os.F_OK)

def set_dir_has_no_diffs(dirname):
	f = file(os.path.join(dirname, ".no_diffs"), "w")
	f.close()

def reset_dir_has_no_diffs(dirname):
	os.unlink(os.path.join(dirname, ".no_diffs"))

def diff_dir(from_dir, to_dir, dir = None, recursive = True):
	if dir:
		from_dir = os.path.join(from_dir, dir)
		to_dir = os.path.join(to_dir, dir)
	print "\r%-130s" % from_dir
	sys.stdout.flush()
	diff = filecmp.dircmp(from_dir, to_dir)
	if not dir_has_no_diffs(to_dir):
		diff_files = diff.diff_files
		if diff_files:
			diff_files.sort()
			print "\n  %s" % from_dir
			sys.stdout.flush()
			for f in diff_files:
				diff_file(os.path.join(from_dir, f),
					  os.path.join(to_dir, f))
		else:
			set_dir_has_no_diffs(to_dir)
	if not recursive:
		return
	common_dirs = diff.common_dirs
	if not common_dirs:
		return
	common_dirs.sort()
	for dir in common_dirs:
		diff_dir(from_dir, to_dir, dir)

def do_diff_dwarfs2ctfs():
	diff_dir(os.path.join(regtest_output_dir, "after", "pahole", "dwarf"),
		 os.path.join(regtest_output_dir, "after", "pahole", "ctf"))

def do_diff_dwarfs():
	diff_dir(os.path.join(regtest_output_dir, "before", "pahole", "dwarf"),
		     os.path.join(regtest_output_dir, "after", "pahole", "dwarf"))

def do_tool(tool, before_after, format, dirname, fname,
	    prepend_obj_dir = False):
	if prepend_obj_dir:
		fname += ".debug"
		fixed_dirname = dirname
	else:
		fixed_dirname = dirname[len_debug_dir:]
	tool_output_dir = os.path.join(regtest_output_dir,
				       before_after, tool, format,
				       fixed_dirname)
	obj_path = os.path.join(dirname, fname)
	if prepend_obj_dir:
		obj_path = os.path.join(regtest_obj_dir, obj_path)
	if os.path.islink(obj_path) or os.path.isdir(obj_path):
		return
	try:
		os.makedirs(tool_output_dir)
	except:
		pass
	if dir_has_no_diffs(tool_output_dir):
		reset_dir_has_no_diffs(tool_output_dir)
	output_file = os.path.join(tool_output_dir, fname[:-6])
	if use_options and tools[tool].has_key(format):
		options = tools[tool][format]
	else:
		options = ""
	command = '%s -F %s %s %s > "%s"' % (tool, format, options,
					     obj_path, output_file)
	if verbose > 1:
		print command
		sys.stdout.flush()
	elif verbose > 0:
		print "%s: %s" % (format,
				  os.path.join(fixed_dirname, fname[:-6]))
	os.system(command)

def do_tool_on_files(arg, dirname, fnames, prepend_obj_dir = False):
	if dirname.find("/.") >= 0:
		return
	tool, before_after = arg
	for fname in fnames:
		if not prepend_obj_dir and fname[-6:] != ".debug":
			continue

		for format in formats:
			do_tool(tool, before_after, format, dirname, fname,
				prepend_obj_dir)

def do_tools(before_after):
	for tool in tools.keys():
		os.path.walk(regtest_obj_dir, do_tool_on_files, (tool, before_after))

def do_ctf(dirname, fname, prepend_obj_dir = False):
	if prepend_obj_dir:
		fname += ".debug"
		fixed_dirname = dirname
	else:
		fixed_dirname = dirname[len_debug_dir:]
	obj_path = os.path.join(dirname, fname)
	if prepend_obj_dir:
		obj_path = os.path.join(regtest_obj_dir, obj_path)

	if os.path.islink(obj_path) or os.path.isdir(obj_path):
		return
	command = 'pahole -Z "%s" 2> /dev/null' % obj_path
	if verbose > 1:
		print command
	elif verbose > 0:
		print os.path.join(fixed_dirname, fname[:-6])
	os.system(command)

def do_ctf_on_files(arg, dirname, fnames, prepend_obj_dir = False):
	if dirname.find("/.") >= 0:
		return
	for fname in fnames:
		if not prepend_obj_dir and fname[-6:] != ".debug":
			continue

		do_ctf(dirname, fname, prepend_obj_dir)

def do_ctfs():
	os.path.walk(regtest_obj_dir, do_ctf_on_files, None)

def sig_exit(sig_number, stack_frame):
	sys.exit(1)

def listdebugs(dirname):
	fnames = []
	for fname in os.listdir(os.path.join(regtest_obj_dir, dirname)):
		if fname[-6:] != ".debug":
			continue
		obj_path = os.path.join(regtest_obj_dir, dirname, fname)
		if os.path.islink(obj_path) or os.path.isdir(obj_path):
			continue
		fnames.append(fname[:-6])
	return fnames

def usage():
	print 'Usage: regtest [OPTIONS]'
	fmt = '\t%-20s %s'
	print fmt % ('-h, --help',	 'Give this help list')
	print fmt % ('-a, --after',	 'Generate new output')
	print fmt % ('-b, --before',	 'Generate old output')
	print fmt % ('-c, --ctf_diff',	 'Diff between DWARF and CTF for new output')
	print fmt % ('-C, --ctf_encode', 'Encode CTF into object files')
	print fmt % ('-d, --diff',	 'Diff between old and new output')
	print fmt % ('-f, --formats',	 'formats used (default: %s)' ','.join(formats))

def main(argv):
	global formats

	for sig in (signal.SIGHUP, signal.SIGINT, signal.SIGTERM):
		signal.signal(sig, sig_exit)

	try:
		short = "habcCdf:"
		long = ("help", "after", "before", "ctf_diff", "ctf_encode",
			"diff", "formats")
		opts, args = getopt.getopt(sys.argv[1:], short, long)
	except getopt.GetoptError, err:
		usage()
		print str(err)
		sys.exit(2)

	for o, a in opts:
		if o in ("-h", "--help"):
			usage()
			return
		elif o in ("-f", "--formats"):
			formats = a.split(',')
		elif o in ("-a", "--after",
			   "-b", "--before",
			   "-c", "--ctf_diff",
			   "-C", "--ctf_encode",
			   "-d", "--diff"):

			if len(args) > 0:
				dirname = args[0]
				if len(args) > 1:
					fnames = args[1:]
				elif o in ('-a', '--after',
					   '-b', '--before',
					   '-C', '--ctf_encode'):
					fnames = listdebugs(dirname)

			if o in ('-b', '--before', '-a', '--after'):
				if o in ('-b', '--before'):
					when = 'before'
				else:
					when = 'after'
				if len(args) > 0:
					for tool in tools.keys():
						arg = (tool, when)
						do_tool_on_files(arg, dirname, fnames, True)
				else:
					do_tools(when)
			elif o in ('-d', '--diff'):
				if len(args) > 0:
					from_dir = os.path.join(regtest_output_dir,
								"before", "pahole",
								"dwarf", dirname)
					to_dir = os.path.join(regtest_output_dir,
							      "after", "pahole",
							      "dwarf", dirname)
					if len(args) > 1:
						for fname in fnames:
							diff_file(os.path.join(from_dir, fname),
								  os.path.join(to_dir, fname))
					else:
						diff_dir(from_dir, to_dir, recursive = False)
				else:
					do_diff_dwarfs()
			elif o in ('-C', 'ctf'):
				if len(args) > 0:
					do_ctf_on_files(None, dirname, fnames, True)
				else:
					do_ctfs()
			elif o in ('-c', 'ctf_diff'):
				if len(args) > 0:
					from_dir = os.path.join(regtest_output_dir,
								"after", "pahole",
								"dwarf", dirname)
					to_dir = os.path.join(regtest_output_dir,
							      "after", "pahole",
							      "ctf", dirname)
					if len(args) > 1:
						for fname in fnames:
							diff_file(os.path.join(from_dir, fname),
								  os.path.join(to_dir, fname))
					else:
						diff_dir(from_dir, to_dir, recursive = False)
				else:
					do_diff_dwarfs2ctfs()

if __name__ == '__main__':
    main(sys.argv)
